package com.linshengjian.snowball.guard;

import com.linshengjian.snowball.guard.filter.LoginAuthenticationFilter;
import com.linshengjian.snowball.guard.filter.TokenAuthenticationFilter;
import com.linshengjian.snowball.guard.filter.VerifyCodeFilter;
import com.linshengjian.snowball.guard.handler.IAuthVerifyHandle;
import com.linshengjian.snowball.guard.handler.IVerifyCodeHandle;
import com.linshengjian.snowball.guard.service.GuardUserService;
import com.linshengjian.snowball.guard.service.IVerifyCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

import java.util.*;

@ConditionalOnBean({IVerifyCodeService.class, IAuthVerifyHandle.class, IVerifyCodeHandle.class})
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableConfigurationProperties(GuardProperties.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private GuardProperties properties;
    @Autowired
    private GuardUserService userService;
    @Autowired
    private IVerifyCodeService verifyCodeService;
    @Autowired
    private IAuthVerifyHandle authVerifyHandle;
    @Autowired
    private IVerifyCodeHandle verifyCodeHandle;

    @Bean
    public UserDetailsService createUserDetailsService() {
        return username -> {
            UserDetails user = userService.findByUserName(username);
            if (user == null)
                throw new UsernameNotFoundException(String.format("没有找到账号：'%s' 对应的用户。", username));
            return user;
        };
    }

    @Bean
    public PasswordEncoder createPasswordEncoder() {
        try {
            return properties.getLogin().getPasswordStrategy().newInstance();
        } catch (Exception e) {
            return new BCryptPasswordEncoder();
        }
    }

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .userDetailsService(createUserDetailsService())
                .passwordEncoder(createPasswordEncoder());
    }

    private String[] configPathHandler(String permitStr) {
        List<String> result = new ArrayList<>();
        if (!StringUtils.isEmpty(permitStr)) {
            result.addAll(Arrays.asList(permitStr.split(",")));
        }

        return result.toArray(new String[result.size()]);
    }

    private void permitHandler(HttpSecurity httpSecurity) throws Exception {
        String[] paths = properties.getPermit().split(",");

        List<String> all = new ArrayList<>();
        Map<String, List<String>> map = new HashMap<>();

        for (String path: paths) {
            if (StringUtils.isEmpty(path))
                continue;

            String[] values = path.split(":");

            if (values.length == 1) {
                all.add(values[0]);
                continue;
            }
            if (values.length == 2) {
                if (!map.containsKey(values[0])) {
                    map.put(values[0], new ArrayList<>());
                }

                List<String> temp = map.get(values[0]);
                temp.add(values[1]);
                continue;
            }
        }

        for (String key : map.keySet()) {

            HttpMethod method = HttpMethod.valueOf(key.toUpperCase());
            if (method == null) continue;

            List<String> value = map.get(key);

            httpSecurity.authorizeRequests().antMatchers(method, value.toArray(new String[value.size()])).permitAll().and();
        }
        if (all.size() > 0)
            httpSecurity.authorizeRequests().antMatchers(all.toArray(new String[all.size()])).permitAll();

        httpSecurity.authorizeRequests().anyRequest().authenticated();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .cors().and()
                // 由于使用的是JWT，我们这里不需要csrf
                .csrf().disable();
                // 基于token，所以不需要session
//                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

//                .authorizeRequests()
                //.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()


                // 允许对于网站静态资源的无授权访问
//                .authorizeRequests().antMatchers(HttpMethod.GET, permitHandler(properties.getPermit().getGet())).permitAll().and()
//                .authorizeRequests().antMatchers(HttpMethod.POST, permitHandler(properties.getPermit().getPost())).permitAll().and()
//                .authorizeRequests().antMatchers(permitHandler(properties.getPermit().getAll())).permitAll()
//                // 除上面外的所有请求全部需要鉴权认证
//                .anyRequest().authenticated()

        permitHandler(httpSecurity);
                // 无权限错误处理
        httpSecurity.exceptionHandling().authenticationEntryPoint(authVerifyHandle)
                // 登陆 成功/失败 后的处理，因为是API的形式所以不用跳转页面
//                .and().formLogin().loginPage(properties.getLoginUrl()).successHandler(authVerifyHandle).failureHandler(authVerifyHandle).permitAll()
                // 登出后的处理
                .and().logout().logoutUrl(properties.getLogin().getUrl().getLogout()).logoutSuccessHandler(authVerifyHandle);

        // 添加JWT filter
//        httpSecurity
//                .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        httpSecurity
                .addFilterAfter(createrTokenAuthenticationFilter(), BasicAuthenticationFilter.class)
                .addFilterBefore(createUserLoginFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(createVerifyCodeFilter(), LoginAuthenticationFilter.class);
        // 禁用缓存
        httpSecurity.headers().cacheControl();
    }

    private TokenAuthenticationFilter createrTokenAuthenticationFilter() {
        return new TokenAuthenticationFilter(properties, userService);
    }

    private LoginAuthenticationFilter createUserLoginFilter() throws Exception {
        LoginAuthenticationFilter filter = new LoginAuthenticationFilter(properties, userService);
        filter.setAuthenticationManager(authenticationManager());
        filter.setAuthenticationSuccessHandler(authVerifyHandle);
        filter.setAuthenticationFailureHandler(authVerifyHandle);
        filter.setFilterProcessesUrl(properties.getLogin().getUrl().getLogin());
        return filter;
    }

    private VerifyCodeFilter createVerifyCodeFilter() {
        VerifyCodeFilter filter = new VerifyCodeFilter(properties, verifyCodeHandle, verifyCodeService);

        String[] urls = configPathHandler(properties.getVerifyCode().getVerifyUrl());
        for (String url: urls) {
            String[] values = url.split(":");
            if (values.length == 2)
                filter.addVerifyPath(values[0], values[1]);
            if (values.length == 1)
                filter.addVerifyPath(values[1]);
        }

        return filter;
    }
}
